using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Documents;

namespace YouVisio.Silverlight.TextDiff
{
    public class WordsRetriever : IWordsRetriever
    {
        private Dictionary<string, int> _textParts;
        private Counter _lastKey;
        private readonly List<string> _currentPartsSequence = new List<string>();
        private readonly List<int> _wordsSequence = new List<int>();
        private readonly HashSet<int> _wordsHash = new HashSet<int>();
        private readonly static Regex _extractWords = new Regex(@"(?<EmptySpace>\s+)|(?<Word>\S+)", RegexOptions.CultureInvariant);
        private const string NEW_LINE = "\r";

        #region Implementation of IPartsRetriever<in Tuple<TextPart,TextPart>,int>

        public string Source { set { this.Set(value, EqualityComparer<int>.Default); } }

        public void Set(string source, IEqualityComparer<int> comparer)
        {
            // do nothing
        }

        public int GetByIndex(int index)
        {
            return this._wordsSequence[index];
        }

        public int Length
        {
            get { return this._wordsSequence.Count; }
        }

        public bool Contains(int part)
        {
            return this._wordsHash.Contains(part);
        }

        public bool IndexIsWithin(int index)
        {
            return index >= 0 && index < this.Length;
        }

        #endregion

        #region Implementation of IWordsRetriever

        public virtual void SetTextParts(Tuple<TextPart, TextPart> tuple, Dictionary<string, int> textParts, Counter lastKey, bool isInitial)
        {
            if (textParts == null) throw new ArgumentException("Text Parts passed to SetTextParts of WordsRetriever cannot be null");
            if (lastKey == null) throw new ArgumentException("Last Key passed to SetTextParts of WordsRetriever cannot be null");
            this._textParts = textParts;
            this._lastKey = lastKey;
            this._currentPartsSequence.Clear();
            this._wordsSequence.Clear();
            if(tuple == null) return;
            SetTextPart(tuple.Item1, isInitial);
            SetTextPart(tuple.Item2, isInitial);
        }

        public virtual List<Word> ExtractWords(DiffPartsList diffList, Operation skipOperation)
        {
            var diffs = diffList.GetEnumerator();
            var list = new List<Word>();
            for (var i = 0; i < this._currentPartsSequence.Count; i++)
            {
                var current = this._currentPartsSequence[i];
                int key;
                if (!this._textParts.TryGetValue(current, out key)) key = -1;
                // this is a word
                if (this._wordsHash.Contains(key))
                {
                    if(!diffs.MoveNext()) return list;
                    var operation = diffs.Current;
                    while (operation == skipOperation)
                    {
                        if (!diffs.MoveNext()) return list;
                        operation = diffs.Current;
                    }
                    list.Add(operation == Operation.Equal
                                 ? new Word { Text = current, IsWord = true }
                                 : new Word { Text = current, IsWord = true, IsChanged = true });
                }
                else
                {
                    list.Add(current == NEW_LINE
                                 ? new Word { Text = current, IsWord = false, IsBreak = true }
                                 : new Word { Text = current, IsWord = false });
                }
            }
            return list;
        }

        #endregion

        

        private void SetTextPart(TextPart part, bool isInitial)
        {
            if (part == null) return;
            var linesArray = isInitial ? part.Initial : part.Changed;
            if (linesArray == null || linesArray.Length == 0) return;
            for(var i = 0; i < linesArray.Length; i++)
            {
                var line = linesArray[i];
                var matches = _extractWords.Matches(line);
                foreach (Match match in matches)
                {
                    var groups = match.Groups;
                    var group = groups["Word"];
                    if (group.Success)
                    {
                        var key = AddNewStringPart(group.Value);
                        this._wordsSequence.Add(key);
                        this._wordsHash.Add(key);
                    }
                    else
                    {
                        group = groups["EmptySpace"];
                        if (group.Success)
                        {
                            AddNewStringPart(group.Value);
                        }
                    }
                }
                AddNewStringPart(NEW_LINE);
            }
        }

        private int AddNewStringPart(string str)
        {
            int key;
            if (!this._textParts.TryGetValue(str, out key))
            {
                key = this._lastKey.Value;
                this._textParts.Add(str, key);
                this._lastKey.Value++;
            }
            this._currentPartsSequence.Add(str);
            
            return key;
        }
    }
}